package online

import (
	"errors"
	"server/common"
	"sync"

	"github.com/sirupsen/logrus"
)

const (
	Pending int = iota
	Calling
	Active
	Negotiating
	Terminating
	Terminated
)

func CallStateToString(state int) string {
	switch state {
	case Pending:
		return "Pending"
	case Calling:
		return "Calling"
	case Active:
		return "Active"
	case Negotiating:
		return "Negotiating"
	case Terminating:
		return "Terminating"
	case Terminated:
		return "Terminated"
	default:
		return "Unknown"
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

type CallData struct {
	CallType     string             // "p2p" | "p2sp"
	CallId       string             // unique identifier for the call
	CalleeId     string             // user_id | room_id
	CalleeMedias []common.MediaInfo // Medias that the callee is publishing
	CallerId     string             // user_id
	CallerMedias []common.MediaInfo // Medias that the caller is publishing
	CallState    int                // Pending, HaveRequest, SendNotify, HaveAck, SendResponse
	Timestamp    int64              // timestamp for the call
	From         common.ServiceInfo // service info of the caller
}

type CallManager struct {
	calls map[string]*CallData // key: call_id
	mtx   sync.RWMutex
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func NewCallManager() *CallManager {
	return &CallManager{
		calls: make(map[string]*CallData),
	}
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) AddCall(callId string, callData CallData) error {
	cm.mtx.Lock()
	defer cm.mtx.Unlock()

	// Check if the call already exists
	if _, exists := cm.calls[callId]; exists {
		common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
			"callId":   callId,
			"callData": callData,
		}).Warn("Call already exists, updating existing call data")
		return errors.New("call already exists")
	}

	cm.calls[callId] = &callData

	common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
		"callId":   callId,
		"callData": callData,
	}).Info("Call added successfully")

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) GetCall(callId string) (*CallData, bool) {
	cm.mtx.RLock()
	defer cm.mtx.RUnlock()

	callData, exists := cm.calls[callId]
	if !exists {
		return nil, false
	}

	return callData, true
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) RemoveCall(callId string) {
	cm.mtx.Lock()
	defer cm.mtx.Unlock()

	if _, exists := cm.calls[callId]; exists {
		delete(cm.calls, callId)
		common.Logger(common.SERVICE_TYPE_ONLINE).WithField("callId", callId).
			Info("Call removed successfully")
	} else {
		common.Logger(common.SERVICE_TYPE_ONLINE).WithField("callId", callId).
			Warn("Attempted to remove a call that does not exist")
	}
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) updateCallState(callId string, newState int) error {
	cm.mtx.Lock()
	defer cm.mtx.Unlock()

	callData, exists := cm.calls[callId]
	if !exists {
		return errors.New("call not found")
	}

	callData.CallState = newState
	cm.calls[callId] = callData

	common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
		"callId":   callId,
		"newState": newState,
	}).Info("Call state updated successfully")

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) GetPeerUserId(callId, userId string) (string, error) {
	cm.mtx.RLock()
	defer cm.mtx.RUnlock()

	callData, exists := cm.calls[callId]
	if !exists {
		return "", errors.New("call not found")
	}

	if userId == callData.CallerId {
		return callData.CalleeId, nil
	} else if userId == callData.CalleeId {
		return callData.CallerId, nil
	}

	return "", errors.New("user_id does not match call participants")
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) AddPublishMedia(reqData common.P2PStartPublishReqData) error {
	cm.mtx.Lock()
	defer cm.mtx.Unlock()

	callData, exists := cm.calls[reqData.CallId]
	if !exists {
		return errors.New("call not found")
	}

	if callData.CallState != Active && callData.CallState != Negotiating {
		return errors.New("call is not in a valid state for adding media")
	}

	if reqData.UserId == callData.CallerId {
		// Check if media already exists for caller
		for _, existingMedia := range callData.CallerMedias {
			if common.CompareMediaInfo(existingMedia, reqData.Media) {
				common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
					"callId":   reqData.CallId,
					"userId":   reqData.UserId,
					"media":    reqData.Media,
					"callData": callData,
				}).Warn("Media already exists for caller, skipping addition")
				return errors.New("media already exists for caller")
			}
		}
		callData.CallerMedias = append(callData.CallerMedias, reqData.Media)
	} else if reqData.UserId == callData.CalleeId {
		// Check if media already exists for callee
		for _, existingMedia := range callData.CalleeMedias {
			if common.CompareMediaInfo(existingMedia, reqData.Media) {
				common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
					"callId":   reqData.CallId,
					"userId":   reqData.UserId,
					"media":    reqData.Media,
					"callData": callData,
				}).Warn("Media already exists for callee, skipping addition")
				return errors.New("media already exists for callee")
			}
		}
		callData.CalleeMedias = append(callData.CalleeMedias, reqData.Media)
	} else {
		return errors.New("user_id does not match call participants")
	}

	common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
		"callId":   reqData.CallId,
		"userId":   reqData.UserId,
		"media":    reqData.Media,
		"callData": callData,
	}).Info("Media added to call successfully")

	return nil
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) RemovePublishMedia(reqData common.P2PStopPublishReqData) error {
	cm.mtx.Lock()
	defer cm.mtx.Unlock()

	callData, exists := cm.calls[reqData.CallId]
	if !exists {
		return errors.New("call not found")
	}

	if reqData.UserId == callData.CallerId {
		// Remove media from caller
		for i, existingMedia := range callData.CallerMedias {
			if common.CompareMediaInfo(existingMedia, reqData.Media) {
				callData.CallerMedias = append(callData.CallerMedias[:i], callData.CallerMedias[i+1:]...)
				common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
					"callId":   reqData.CallId,
					"userId":   reqData.UserId,
					"media":    reqData.Media,
					"callData": callData,
				}).Info("Media removed from caller successfully")
				return nil
			}
		}
	} else if reqData.UserId == callData.CalleeId {
		// Remove media from callee
		for i, existingMedia := range callData.CalleeMedias {
			if common.CompareMediaInfo(existingMedia, reqData.Media) {
				callData.CalleeMedias = append(callData.CalleeMedias[:i], callData.CalleeMedias[i+1:]...)
				common.Logger(common.SERVICE_TYPE_ONLINE).WithFields(logrus.Fields{
					"callId":   reqData.CallId,
					"userId":   reqData.UserId,
					"media":    reqData.Media,
					"callData": callData,
				}).Info("Media removed from callee successfully")
				return nil
			}
		}
	} else {
		return errors.New("user_id does not match call participants")
	}

	return errors.New("media not found")
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) HasPublishMedia(callId string, userId string, media common.MediaInfo) bool {
	cm.mtx.RLock()
	defer cm.mtx.RUnlock()

	callData, exists := cm.calls[callId]
	if !exists {
		return false
	}

	if userId == callData.CallerId {
		for _, existingMedia := range callData.CallerMedias {
			if common.CompareMediaInfo(existingMedia, media) {
				return true
			}
		}
	} else if userId == callData.CalleeId {
		for _, existingMedia := range callData.CalleeMedias {
			if common.CompareMediaInfo(existingMedia, media) {
				return true
			}
		}
	}

	return false
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) CallExists(callId string) bool {
	cm.mtx.RLock()
	defer cm.mtx.RUnlock()

	_, exists := cm.calls[callId]
	return exists
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) UserInCall(callId string, userId string) bool {
	cm.mtx.RLock()
	defer cm.mtx.RUnlock()

	callData, exists := cm.calls[callId]
	if !exists {
		return false
	}

	return userId == callData.CallerId || userId == callData.CalleeId
}

// -------------------------------------------------------------------------------------------------
//
// -------------------------------------------------------------------------------------------------
func (cm *CallManager) PublishMediaExists(callId string, userId string, media common.MediaInfo) bool {
	cm.mtx.RLock()
	defer cm.mtx.RUnlock()

	callData, exists := cm.calls[callId]
	if !exists {
		return false
	}

	if userId == callData.CallerId {
		for _, existingMedia := range callData.CallerMedias {
			if common.CompareMediaInfo(existingMedia, media) {
				return true
			}
		}
	} else if userId == callData.CalleeId {
		for _, existingMedia := range callData.CalleeMedias {
			if common.CompareMediaInfo(existingMedia, media) {
				return true
			}
		}
	}

	return false
}
